#include "common/api/client.h"
#include "common/elements/element_type.hpp"
#include "common/log/log.h"
#include "common/more/json.h"
#include "common/more/uuid.h"

#include "ui/translations.h"

#include "ui_session.h"

namespace
{
template<typename T>
void reload(
    std::shared_ptr<Api::Client> datenaroGate,
    const Uuids::Uuid& sessionUuid,
    T& container,
    Elements::ElementType type,
    const std::set<unsigned>& ids
)
{
    const std::vector<unsigned> elementsIds(ids.cbegin(), ids.cend());
    const auto elements = datenaroGate->getElements(sessionUuid, type, elementsIds);

    std::set<unsigned> changedIds;
    for (auto element : elements)
        changedIds.insert(element->id());

    std::vector<unsigned> idsToRemove;
    std::set_difference(
        elementsIds.cbegin(),
        elementsIds.cend(),
        changedIds.cbegin(),
        changedIds.cend(),
        std::inserter(idsToRemove, idsToRemove.begin())
    );

    container.remove(idsToRemove);
    container.insertCast(elements);
};
} // namespace

UiSession::UiSession(
    const Uuids::Uuid& sessionUuid,
    unsigned userId,
    std::shared_ptr<Api::Client> datenaroGate
)
    : m_sessionUuid(sessionUuid)
    , m_userId(userId)
    , m_datenaroGate(datenaroGate)
{
    loadPermanentElements();
    m_permanentElementsUpdateThread.reset(new std::thread(
        &UiSession::updateLoop, this
    ));
}

UiSession::~UiSession()
{
    m_isRunning = false;
    m_permanentElementsUpdateThread->join();
}

CashManager& UiSession::cashManager()
{
    return m_cashManager;
}

Elements::UserPtr UiSession::user() const
{
    return m_cashManager.users.element(m_userId);
}

crow::json::wvalue UiSession::translation() const
{
    if (const auto user = this->user())
        return Ui::Translations::getInstance().data(user->uiLang());

    return {};
}

void UiSession::forceUpdate()
{
    if (m_isRunning)
        update();
}

UiSessionMessageList& UiSession::sessionMessages()
{
    return m_sessionMessages;
}

void UiSession::loadPermanentElements()
{
    m_cashManager.edges.clear();
    m_cashManager.fieldTypes.clear();
    m_cashManager.itemTypes.clear();
    m_cashManager.roles.clear();
    m_cashManager.roleMenuItems.clear();
    m_cashManager.rules.clear();
    m_cashManager.ruleItemTypes.clear();
    m_cashManager.ruleProjects.clear();
    m_cashManager.ruleStates.clear();
    m_cashManager.states.clear();
    m_cashManager.teams.clear();
    m_cashManager.users.clear();
    m_cashManager.userTeamRoles.clear();
    m_cashManager.workflows.clear();

    auto load = [this](auto& container, Elements::ElementType type)
    {
        const auto ids = m_datenaroGate->getIds(m_sessionUuid, type);
        const auto elements = m_datenaroGate->getElements(m_sessionUuid, type, ids);
        container.insertCast(elements);
    };
    load(m_cashManager.edges, Elements::ElementType::Edge);
    load(m_cashManager.fieldTypes, Elements::ElementType::FieldType);
    load(m_cashManager.itemTypes, Elements::ElementType::ItemType);
    load(m_cashManager.roles, Elements::ElementType::Role);
    load(m_cashManager.roleMenuItems, Elements::ElementType::RoleMenuItem);
    load(m_cashManager.rules, Elements::ElementType::Rule);
    load(m_cashManager.ruleItemTypes, Elements::ElementType::RuleItemType);
    load(m_cashManager.ruleProjects, Elements::ElementType::RuleProject);
    load(m_cashManager.ruleStates, Elements::ElementType::RuleState);
    load(m_cashManager.states, Elements::ElementType::State);
    load(m_cashManager.teams, Elements::ElementType::Team);
    load(m_cashManager.users, Elements::ElementType::User);
    load(m_cashManager.userTeamRoles, Elements::ElementType::UserTeamRole);
    load(m_cashManager.workflows, Elements::ElementType::Workflow);
}

void UiSession::updateLoop()
{
    while (m_isRunning)
    {
        std::this_thread::sleep_for(std::chrono::seconds(10));
        update();
    }
}

void UiSession::update()
{
    std::unique_lock locker(m_updateLock);
    for (const auto& [type, ids] : m_datenaroGate->hasChanges(m_sessionUuid))
    {
        switch (type)
        {
        case Elements::ElementType::Edge:
            reload(m_datenaroGate, m_sessionUuid, m_cashManager.edges, type, ids);
            break;
        case Elements::ElementType::FieldType:
            reload(m_datenaroGate, m_sessionUuid, m_cashManager.fieldTypes, type, ids);
            break;
        case Elements::ElementType::ItemType:
            reload(m_datenaroGate, m_sessionUuid, m_cashManager.itemTypes, type, ids);
            break;
        case Elements::ElementType::Role:
            reload(m_datenaroGate, m_sessionUuid, m_cashManager.roles, type, ids);
            break;
        case Elements::ElementType::RoleMenuItem:
            reload(m_datenaroGate, m_sessionUuid, m_cashManager.roleMenuItems, type, ids);
            break;
        case Elements::ElementType::Rule:
            reload(m_datenaroGate, m_sessionUuid, m_cashManager.rules, type, ids);
            break;
        case Elements::ElementType::RuleItemType:
            reload(m_datenaroGate, m_sessionUuid, m_cashManager.ruleItemTypes, type, ids);
            break;
        case Elements::ElementType::RuleProject:
            reload(m_datenaroGate, m_sessionUuid, m_cashManager.ruleProjects, type, ids);
            break;
        case Elements::ElementType::RuleState:
            reload(m_datenaroGate, m_sessionUuid, m_cashManager.ruleStates, type, ids);
            break;
        case Elements::ElementType::State:
            reload(m_datenaroGate, m_sessionUuid, m_cashManager.states, type, ids);
            break;
        case Elements::ElementType::Team:
            reload(m_datenaroGate, m_sessionUuid, m_cashManager.teams, type, ids);
            break;
        case Elements::ElementType::User:
            reload(m_datenaroGate, m_sessionUuid, m_cashManager.users, type, ids);
            break;
        case Elements::ElementType::UserTeamRole:
            reload(m_datenaroGate, m_sessionUuid, m_cashManager.userTeamRoles, type, ids);
            break;
        case Elements::ElementType::Workflow:
            reload(m_datenaroGate, m_sessionUuid, m_cashManager.workflows, type, ids);
            break;
        default:
            break;
        }
    }
}
